#include "http_download.h"

const char* DFJsonUrl =    "http://182.254.130.180/dfrobot.json";
const char* AzureJsonUrl = "http://182.254.130.180/azure.json";
const char* AwsJsonUrl =   "http://182.254.130.180/aws.json";

uint8_t recvBuffer[1024];

String jsonBinMD5;
char *jsonBinFileUrl = NULL;
int jsonBinVersion = 1;

int platformReady = PLATFORM_CURRENT;
char platformVersionReady[10]="0";
/*
typedef struct jsonData{
  String jsonBinMD5;
  char *jsonBinFileUrl;
  int jsonBinVersion;
}JsonData;

JsonData * jsondata;
*/
String downBinMD5;
String readFlashMD5;


String typechange(int a)
{
  String b;
  char c[9];
  char d[9];
  int i=0;
  while(a!=0)
  {
    c[i]=a&(0xf);
    if(c[i]>9)
      c[i]+='a'-10;
    else
      c[i]+='0';

    a=a>>4;
    i++;
    if(i==9)
      break;
  }
  c[i]='\0';
  i--;
  int k=0;
  int t=i;
  for(;k<=t;k++)
  {
    d[k]=c[i];
    i--;
  }
  d[k]='\0';
  String b1(d);
  return b1;
}

String uint8ToString(uint8_t* buff)
{
  int i=0;
  String d;
  for(i=0;i<16;i++){
      int t = buff[i];
      if(t>=16) d+=typechange(t);
      else{
        if(t == 0) d+="00"+typechange(t);
        else  d+="0"+typechange(t);
      }
  }
  JHDBG("-------------%s\n",d.c_str());
  return d.c_str();
}

int jsonParse(char *parsemsg)
{
  cJSON *root = cJSON_Parse(parsemsg);
  if(!root){
    JHDBG("get root fail\n"); 
    return -1;
  }
  cJSON *js_list = cJSON_GetObjectItem(root,"8266edu");
  if(!js_list){
    JHDBG("get 8266edu fail\n");
    cJSON_Delete(root);
    return -1;  
  }

  int array_size = cJSON_GetArraySize(js_list);
  JHDBG("array_size = %d\n",array_size);
  int i=0;
  int highestVersion = NOWVERSION;
  cJSON *item,*it, *js_version, *js_file, *js_md5;
  char *p=NULL;
  for(i=0; i< array_size; i++) {
     item = cJSON_GetArrayItem(js_list, i);
     if(!item) {
         cJSON_Delete(root);
         return -1;
     }
     p = cJSON_PrintUnformatted(item);
     if(p==NULL){
         cJSON_Delete(root);
         return -1;
     }
     it = cJSON_Parse(p);
     if(!it)
         continue ;
     js_version = cJSON_GetObjectItem(it, "version");
   //  if(js_version->valueint > highestVersion)
     //   highestVersion = js_version->valueint;
     if(!js_version){
       cJSON_Delete(root);
       JHDBG("js_version fail\n");
       return -1;
     }
     JHDBG("version is %d,now version is %d\n",js_version->valueint,NOWVERSION);
     js_file = cJSON_GetObjectItem(it, "file");
     if(!js_file){
       cJSON_Delete(root);
       JHDBG("js_file fail\n");
       return -1;
     }
     JHDBG("file is %s\n",js_file->valuestring);
     js_md5 = cJSON_GetObjectItem(it, "md5");
     if(!js_md5){
       cJSON_Delete(root);
       JHDBG("js_md5 fail\n");
       return -1;
     }
     JHDBG("md5 is %s\n",js_md5->valuestring); 

    if(strcmp(platformVersionReady,"0")==0){
      if(js_version->valueint > highestVersion ){
        highestVersion = js_version->valueint;
        if(jsonBinFileUrl){
          free(jsonBinFileUrl);  
          jsonBinFileUrl = NULL;
        }
        jsonBinFileUrl = (char *)malloc(strlen(js_file->valuestring)+1);
        if(jsonBinFileUrl){
          strcpy(jsonBinFileUrl,js_file->valuestring);
          jsonBinVersion = js_version->valueint;
          jsonBinMD5 = (String)js_md5->valuestring;
          JHDBG("jsonBinMD5 is %s\n",jsonBinMD5.c_str());
          JHDBG("binFileUrl = %s\n",jsonBinFileUrl);
        }else{
          cJSON_Delete(root);
          JHDBG("malloc err\n");
          return -1;  
        }
      }
    }else{
      char buf[10]={0};
      JHDBG("js_version->valueint:%d\n",js_version->valueint);

      int nowversionTen = (int)js_version->valueint/10;
      int nowversionBits = js_version->valueint%10;
      sprintf(buf,"%d.%d",nowversionTen,nowversionBits); 

      JHDBG("*************");
      JHDBG("buf:%s\n",buf);
      JHDBG("platformVersionReady:%s\n",platformVersionReady);
      if(strcmp(buf,platformVersionReady)==0){
        jsonBinFileUrl = (char *)malloc(strlen(js_file->valuestring)+1);
        if(jsonBinFileUrl){
          strcpy(jsonBinFileUrl,js_file->valuestring);
          jsonBinVersion = js_version->valueint;
          jsonBinMD5 = (String)js_md5->valuestring;
          JHDBG("jsonBinMD5 is %s\n",jsonBinMD5.c_str());
          JHDBG("binFileUrl = %s\n",jsonBinFileUrl);
          cJSON_Delete(root);
          return 1;
        }else{
          cJSON_Delete(root);
          JHDBG("malloc err\n");
          return -1;  
        }  
      }else{
        JHDBG("*************");  
      }
    }
  }
  if(platformReady == PLATFORM_DFROBOT){
    if(highestVersion > NOWVERSION) {
      findNewVersion();
      cJSON_Delete(root);
      return 1;
    }//else  noNewVersion();
  }
  cJSON_Delete(root);
  return 0;
}


bool downJson(WiFiClient& client)
{
  uint8_t downTimeOut = 0;
  HTTPClient hc;
  if(platformReady == PLATFORM_DFROBOT){
    hc.begin(DFJsonUrl);
  }else if(platformReady == PLATFORM_AZURE){
    hc.begin(AzureJsonUrl);  
  }else if(platformReady == PLATFORM_AWS){
    hc.begin(AwsJsonUrl); 
  }else{
    return false;  
  }
  JHDBG("Query file information ... ");
  int code;
  if ( (code = hc.GET()) == 200 )
  {
    JHDBG("OK\n");
  }
  else
  {
    JHDBG("fail (code = %d)\n", code);
    return false;
  }

  client = hc.getStream();
  size_t fsz = hc.getSize(), recvd = 0, peroidsize = 0 ;
  JHDBG("fsz=%d\n",fsz);
  int sectornum = 0,erase=0,eraseSector = 0;
  memset(recvBuffer,0,sizeof(recvBuffer)); 
  int peroid = 500;
  unsigned int speed;
  unsigned long start = millis();
  Timer timer;
  timer.setInterval(peroid);
  md5_context_t ctx;
  MD5Init(&ctx);

  int number=0;
  while(client)
  {
    size_t nbytes;
    static uint8_t buffer[512];
    delay(10);
    size_t remain = fsz - recvd;
    if (timer.check() || recvd == fsz)
    {
      timer.tick();
      if (recvd != fsz)
      speed = (unsigned int)((float)peroidsize / peroid * 1000 / 1024);
      uint8_t updatePercent = (uint8_t)round(recvd*100.0f/fsz);
      if(speed == 0){
        downTimeOut++;
        if(downTimeOut == 50){
          return false;  
        }     
      }
      peroidsize = 0;
      JHDBG("\033[1K\rStart download json ...%dKB/s  %d%(%d/%d)\n",speed,updatePercent,recvd,fsz);
      if (recvd == fsz)
      {
        unsigned long elapsed = millis() - start;
        MD5Final((uint8_t*)buffer, &ctx);
        JHDBG("\n");
        JHDBG("Download successfully\n");
        speed = (unsigned int)((float)fsz / (elapsed * 1000 / 1024));
        //Serial << "File md5: " << HexPrinter(Printer(buffer, 16)) << endl;
        JHDBG("Average speed:%dKB/s\n",speed);
        JHDBG("Elapsed time:");
        JHDBG("%.3fs\n",elapsed / 1000.0f);
        break;
      } 
    }

    if ((nbytes = client.read(buffer, remain < sizeof(buffer) ? remain : sizeof(buffer))) > 0) 
    {
      recvd += nbytes;
      peroidsize += nbytes;
      for(int i=0;i<nbytes;i++){
        if((i+number)>=1024)return false;
        recvBuffer[i+number] = buffer[i];  
      }
      number+=nbytes;
      MD5Update(&ctx, (uint8_t*)buffer, nbytes);
    } 
  }

  return true;
}

bool readMD5(size_t fsz,int sectornum)
{
  int numberFor = (int)fsz/512;
  int numberLast = (int)fsz-(numberFor*512);
  JHDBG("read fsz=%d\n",fsz);
  md5_context_t ctx;
  MD5Init(&ctx);
  int number=0;
  size_t recvd=0;
  int i = 0;
  while(recvd != fsz){
    uint8_t flashBuffer[512];
    
    for(i=0; i<numberFor; i++){
      bool writenum = ESP.flashRead((ENDADDRESS-ONESECTORSIZE*sectornum)+number,(uint32_t *)flashBuffer,512);
  
      delay(10);
      MD5Update(&ctx, (uint8_t*)flashBuffer, 512);
      number+=512;
      JHDBG("----1---fsz=%d,number=%d,recvd=%d\n",fsz,number,recvd);
    }
    if(i == numberFor){
      ESP.flashRead((ENDADDRESS-ONESECTORSIZE*sectornum)+number,(uint32_t *)flashBuffer,numberLast);
      MD5Update(&ctx, (uint8_t*)flashBuffer, numberLast);
      recvd+=numberLast;
      number+=numberLast;
      recvd=number;
      JHDBG("----2---fsz=%d,number=%d,recvd=%d\n",fsz,number,recvd);
    }
    if(recvd == fsz){
      MD5Final((uint8_t*)flashBuffer, &ctx);
      readFlashMD5 = uint8ToString(flashBuffer);
      JHDBG("readFlashMD5=%s\n",readFlashMD5.c_str());
    }
  }
}

bool downBin(WiFiClient& client)
{
  uint8_t downTimeOut = 0;
  HTTPClient hc;
  hc.begin(jsonBinFileUrl);
  JHDBG("Query file information ... \n");
  int code;
  if ( (code = hc.GET()) == 200 )
  {
    JHDBG("OK\n");
  }
  else
  {
    JHDBG("fail (code = %d)\n", code);
    return false;
  }

  client = hc.getStream();
  size_t fsz = hc.getSize(), recvd = 0, peroidsize = 0 ;
  JHDBG("bin fsz=%d\n",fsz);
  int sectornum = 0,erase=0,eraseSector = 0;
  sectornum = (int)fsz/ONESECTORSIZE;
  if(sectornum*ONESECTORSIZE<fsz)
    sectornum += 1; 
  eraseSector = sectornum;
  JHDBG("downbin size = %d,sectornum size=%d\n",fsz,sectornum);
  int peroid = 200;
  unsigned int speed;
  unsigned long start = millis();
  Timer timer;
  timer.setInterval(500);
  md5_context_t ctx;
  MD5Init(&ctx);

  pinMode(REDLED,INPUT);
  pinMode(GREENLED,INPUT);
  int number=0;
  while(client)
  {
    size_t nbytes;
    static uint8_t buffer[512];
    delay(10);
    size_t remain = fsz - recvd;
    if (timer.check() || recvd == fsz)
    {
      timer.tick();
      if (recvd != fsz)
      speed = (unsigned int)((float)peroidsize / peroid * 1000 / 1024);
      
      uint8_t updatePercent = (uint8_t)round(recvd*100.0f/fsz);
      if(updatePercent != ((uint8_t)100))
        sendUpdatePercent(updatePercent);
      if(speed == 0){
        downTimeOut++;
        if(downTimeOut == 50){
          sendUpdatePercent(0xff);
          return false;  
        }     
      }
      
      peroidsize = 0;
      JHDBG("\033[1K\rStart download json ...%dKB/s  %d%(%d/%d)\n",speed,(unsigned int)round(recvd*100.0f/fsz),recvd,fsz);
      digitalWrite(BLUELED, !digitalRead(BLUELED));//jhdebug
      delay(10);
      if (recvd == fsz)
      {
      unsigned long elapsed = millis() - start;
      MD5Final((uint8_t*)buffer, &ctx);
      JHDBG("\n");
      JHDBG("Download successfully\n");
      speed = (unsigned int)((float)fsz / (elapsed * 1000 / 1024));
      
      //Serial << "File md5: " << HexPrinter(Printer(buffer, 16)) << endl;
      downBinMD5 = uint8ToString(buffer);
      //JHDBG("downBinMD5=%s\n",downBinMD5.c_str());

      JHDBG("Average speed:%dKB/s\n",speed);
      JHDBG("Elapsed time:");
      JHDBG("%.3fs\n",elapsed / 1000.0f);
      break;
      } 
    }
    if ((nbytes = client.read(buffer, remain < sizeof(buffer) ? remain : sizeof(buffer))) > 0) 
    {
      recvd += nbytes;
      peroidsize += nbytes;

      if(erase == 0){
        erase = 0;  
        bool result = ESP.flashEraseSector(256-eraseSector);//1 sector=4096=512*8
        //if(result) JHDBG("*********erase ok*************\n");
        eraseSector--;
      }
      erase++;
      if(erase == 8)  erase = 0;

      bool writenum = ESP.flashWrite((ENDADDRESS-ONESECTORSIZE*sectornum)+number,(uint32_t *)buffer,nbytes);
      //if(writenum)  JHDBG("***************write ok***************\n");
      number+=nbytes;
      //delay(10);
      MD5Update(&ctx, (uint8_t*)buffer, nbytes);
    }
  }
  //digitalWrite(UPDATE_LED, HIGH);
  pinMode(REDLED,OUTPUT);
  pinMode(GREENLED,OUTPUT);
  setColor(2);
  readMD5(fsz,sectornum);

  if(readFlashMD5 != downBinMD5){
    JHDBG("MD5 readFlashMD5 downBinMD5 err\n");
    sendUpdatePercent(0xff);
    return false;  
  }
  if(readFlashMD5 != jsonBinMD5){
    JHDBG("MD5 jsonMD5 readFlashMD5 err\n"); 
    sendUpdatePercent(0xff);
    return false; 
  }
  JHDBG("jsonMD5 = readFlashMD5\n"); 
  sendUpdatePercent(100);
  delay(10);
  eboot_command ebcmd;
  ebcmd.action = ACTION_COPY_RAW;
  ebcmd.args[0] = ENDADDRESS-ONESECTORSIZE*sectornum;
  ebcmd.args[1] = 0x00000;
  ebcmd.args[2] = fsz;
  eboot_command_write(&ebcmd);
  delay(10);
  return true;  
}

int httpGetJson(WiFiClient& client)
{
  delay(1000);
  bool state;
  int ret = -1;
  state = downJson(client);
  if(state){
    JHDBG("%s,%d,%d\n",recvBuffer,sizeof(recvBuffer),strlen((char *)recvBuffer));                       
    int allowUpdataNewVersion = jsonParse((char *)recvBuffer); 
    if(allowUpdataNewVersion == 1) {
      //digitalWrite(UPDATE_LED,HIGH);
      ret = 1;
      //has new version = true
      /*
      if(downBin(client)){
        delay(100);
        voidFunc func = (voidFunc)WDT;
        func();
        while(1);
      }else{
        JHDBG("downbin fail\n");  
      }*/
    }else if(allowUpdataNewVersion == 0){
      ret = 0;  
    }else{
    }
  }else{
    JHDBG("downjson fail\n"); 
  }
  return ret;
} 





